package han.Chensing.CCirnosLibrary;

import java.io.File;
import java.io.IOException;
import java.security.InvalidAlgorithmParameterException;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.util.ArrayList;
import java.util.List;
import java.util.Stack;

import javax.crypto.NoSuchPaddingException;

import han.Chensing.CCirnosLibrary.func.NodeBatchDownloadCallback;
import han.Chensing.CCirnosLibrary.func.NodeDownloadCallback;
import han.Chensing.CCirnosLibrary.map.ItemDecInfo;
import han.Chensing.CCirnosLibrary.map.ItemMap;
import han.Chensing.CCirnosLibrary.map.ItemNode;
import han.Chensing.CCirnosLibrary.util.IOUtil;
import han.Chensing.CCirnosLibrary.util.NetUtil;

public class CCirnoLibrary {
    public static final String DEFAULT_ADDRESS="https://gitee.com/CCirno/ccirnos-library-res/raw/master/lib/";
    public static final String INDEX_MAGIC="CCirnosLibraryIndexFile";

    private String path;
    private String menuPath;
    private ItemMap itemMap;
    private boolean local=false;

    public CCirnoLibrary(String path){
        this.path=fixPath(path);
        this.menuPath="index";
        this.itemMap=new ItemMap();
    }

    public CCirnoLibrary(){
        this(DEFAULT_ADDRESS);
    }

    public void unload(){
        itemMap.unload();
    }

    @SuppressWarnings("ResultOfMethodCallIgnored")
    public void downloadListNode(ItemNode itemNode, File dstDir, NodeBatchDownloadCallback batchDownloadCallback){
        Stack<File> fileStack=new Stack<>();
        fileStack.push(dstDir);
        NodeDownloadCallback downloadCallback=
                batchDownloadCallback!=null?new NodeDownloadCallback() {
                    @Override
                    public void onCopy(int len) {
                    }
                    @Override
                    public void onProgressUpdate(int blockNo, int blockAll) {
                        batchDownloadCallback.onPartProgressUpdate(blockNo, blockAll);
                    }
                    @Override
                    public void onComplete(File dst) {
                        batchDownloadCallback.onComplete(dst);
                    }
                }:null;
        itemNode.query((queryType, node, parent) -> {
            switch (queryType) {
                case MenuIn:
                    File file = new File(fileStack.peek(), node.getName());
                    file.mkdir();
                    fileStack.push(file);
                    break;
                case MenuOut:
                    fileStack.pop();
                    break;
                case Node:
                    if (batchDownloadCallback!=null)
                        batchDownloadCallback.onProgressUpdate(node);
                    File outF=new File(fileStack.peek(),node.getOriginalName());
                    try {
                        downloadNode(node,outF,downloadCallback);
                    } catch (Exception e) {
                        e.printStackTrace();
                    }
            }
        });
    }

    @SuppressWarnings("ResultOfMethodCallIgnored")
    public void downloadNode(ItemNode itemNode, File dst, NodeDownloadCallback downloadCallback) throws Exception {
        if (itemNode.isList())
            return;
        File trueDst;
        ItemDecInfo itemDecInfo = itemNode.getItemDecInfo();
        boolean encrypted = itemNode.getItemFlag().containsFlag("encrypted");
        if (encrypted) {
            if (itemDecInfo == null)
                throw new IOException("No decrypting info");
            trueDst=new File(dst+"$temp");
        }else {
            trueDst=dst;
        }
        if (itemNode.getItemFlag().containsFlag("blocks")){
            String path = this.path + itemNode.getLink() + "/block";
            String blockInfo =
                    local?
                            IOUtil.readFirstLineFromFile(new File(path+"s"))
                            :NetUtil.downloadFirstLineFromNet(path+"s");
            if (blockInfo==null)
                throw new IOException("Invalid blocks info");
            int blocks = Integer.parseInt(blockInfo);
            for(int i=0;i<=blocks;i++) {
                if (!local)
                    NetUtil.downloadFileFromNet(path + "" + i, trueDst);
                else
                    NetUtil.downloadFileFromLocal(path + "" + i, trueDst);
                if (downloadCallback!=null)downloadCallback.onProgressUpdate(i+1,blocks);
            }
        }else {
            String path = this.path + itemNode.getLink();
            if (!local)
                NetUtil.downloadFileFromNet(path, trueDst);
            else
                NetUtil.downloadFileFromLocal(path,trueDst);
        }
        if (encrypted){
            IOUtil.decryptFile(trueDst,dst,itemDecInfo);
            trueDst.delete();
        }
        if (downloadCallback!=null)downloadCallback.onComplete(trueDst);
    }

    public String fixPath(String oriPath){
        String path=oriPath;
        if (!path.endsWith("/"))
            path+="/";
        return path;
    }

    public void fetchMenuList(String address,String key)
            throws IOException, NoSuchPaddingException, NoSuchAlgorithmException, InvalidKeyException,
            InvalidAlgorithmParameterException {
        local=false;
        setPath(address);
        setMenuPath(key==null?"index":"eIndex");
        String path = fixPath(address) + menuPath;
        ArrayList<String> strings =
            key==null?
                NetUtil.downloadLinesFromNet(path):
                NetUtil.downloadEncryptedLinesFromNet(path,key);
        fetchMenuList(strings);
    }

    public void fetchMenuList()
            throws IOException, NoSuchPaddingException, NoSuchAlgorithmException, InvalidKeyException,
            InvalidAlgorithmParameterException {
        setPath(DEFAULT_ADDRESS);
        fetchMenuList(path,null);
    }

    public void fetchMenuList(List<String> strings){
        itemMap.analyze(strings,path);
    }

    public void fetchMenuList(File dic,String key)
            throws IOException, NoSuchPaddingException, NoSuchAlgorithmException, InvalidKeyException,
            InvalidAlgorithmParameterException {
        if (!dic.isDirectory())
            return;
        local=true;
        path=fixPath(dic.getAbsolutePath());
        setMenuPath(key==null?"index":"eIndex");
        File file = new File(path + menuPath);
        fetchMenuList(
                key==null?
                        IOUtil.readLinesFromFile(file):
                        IOUtil.readEncryptedLinesFromFile(file,key));
    }

    public boolean isLocal() {
        return local;
    }

    public void setLocal(boolean local) {
        this.local = local;
    }

    public String getMenuPath() {
        return menuPath;
    }

    public void setMenuPath(String menuPath) {
        this.menuPath = menuPath;
    }

    public String getPath() {
        return path;
    }

    public void setPath(String path) {
        this.path = path;
    }

    public ItemMap getItemMap() {
        return itemMap;
    }

    public void setItemMap(ItemMap itemMap) {
        this.itemMap = itemMap;
    }
}
